package com.wangzz.mallchat.common.user.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.wangzz.mallchat.common.user.dao.UserDao;
import com.wangzz.mallchat.common.user.domain.entity.User;
import com.wangzz.mallchat.common.user.service.LoginService;
import com.wangzz.mallchat.common.user.service.WebSocketService;
import com.wangzz.mallchat.common.user.service.adapter.WebSocketAdapter;
import com.wangzz.mallchat.common.websocket.domain.dto.WSChannelExtraDto;
import com.wangzz.mallchat.common.websocket.domain.vo.resp.WSBaseResp;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.SneakyThrows;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class WebSocketServiceImpl implements WebSocketService {

    @Autowired
    @Lazy
    private WxMpService wxMpService;

    @Autowired
    private LoginService loginService;

    @Autowired
    private UserDao userDao;

    // 使用并发map保存用户连接（包括登录态/游客）
    public static final ConcurrentHashMap<Channel, WSChannelExtraDto> LOGIN_MAP = new ConcurrentHashMap<>();
    public static final int MAXIMUM_SIZE = 1000;
    public static final Duration DURATION = Duration.ofHours(1);
    // 临时存储code和Channel关系
    public static final Cache<Integer, Channel> TEMP_LOGIN_MAP = Caffeine.newBuilder()
            .maximumSize(MAXIMUM_SIZE)
            .expireAfterWrite(DURATION)
            .build();

    @Override
    public void connect(Channel channel) {
        // 先存储null的用户信息，当用户授权后再添加用户信息
        LOGIN_MAP.put(channel, new WSChannelExtraDto());
    }

    @SneakyThrows
    @Override
    public void handleLoginReq(Channel channel) {
        // 1.生成一个code和channel绑定
        // 防止不断登录，内存溢出，设置过期时间。引入caffine
        Integer code = generateLoginCode(channel);
        // 2.生成二维码
        WxMpQrCodeTicket wxMpQrCodeTicket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(code, (int) DURATION.getSeconds());

        sendMsg(channel, WebSocketAdapter.buildResp(wxMpQrCodeTicket));
    }

    @Override
    public void handleLoginSuccess(Integer code, Long uid) {
        // 获取用户channel
        Channel channel = TEMP_LOGIN_MAP.getIfPresent(code);
        if (Objects.isNull(channel)) {
            return;
        }
        // 移除临时code和channel关联关系
        TEMP_LOGIN_MAP.invalidate(code);

        User user = userDao.getById(uid);
        // 调用登录模块获取token
        String token = loginService.login(uid);
        // 给客户端发送登录成功后的消息，并绑定用户和channel的关系
        loginSuccess(channel, user, token);
    }

    @Override
    public void remove(Channel channel) {
        LOGIN_MAP.remove(channel);
        // todo 处理用户下线操作
    }

    @Override
    public void waitAuthorize(Integer code) {
        // 获取用户channel
        Channel channel = TEMP_LOGIN_MAP.getIfPresent(code);
        if (Objects.isNull(channel)) {
            return;
        }
        // 推送消息
        sendMsg(channel, WebSocketAdapter.buildAuthorizeResp());
    }

    /**
     * 连接认证
     *
     * @param channel
     * @param token
     */
    @Override
    public void handleAuthorizeReq(Channel channel, String token) {
        // 校验用户是否存在
        Long uid = loginService.getValidUid(token);
        if (Objects.nonNull(uid)) {
            // 存在
            // 返回用户信息
            User user = userDao.getById(uid);
            // 绑定uid和channel的关联关系，并推送登录成功消息
            loginSuccess(channel, user, token);
        } else {
            // 不存在
            // 推送消息给前端删除token
            sendMsg(channel, WebSocketAdapter.buildInvalidTokenResp());
        }
    }

    private void loginSuccess(Channel channel, User user, String token) {
        // 绑定认证登录关系
        WSChannelExtraDto wsChannelExtraDto = LOGIN_MAP.get(channel);
        wsChannelExtraDto.setUid(user.getId());
        // todo 用户上线成功事件
        // 推送登录消息
        sendMsg(channel, WebSocketAdapter.buildResp(user, token));
    }

    private void sendMsg(Channel channel, WSBaseResp<?> resp) {
        // 4.发消息给客户端
        channel.writeAndFlush(new TextWebSocketFrame(JSONUtil.toJsonStr(resp)));
    }

    /**
     * 生成code并绑定和channel的关系，将来code和openid进行绑定来获取channel
     *
     * @param channel
     * @return
     */
    private Integer generateLoginCode(Channel channel) {
        Integer code;
        do {
            code = RandomUtil.randomInt(Integer.MAX_VALUE);
        } while (Objects.nonNull(TEMP_LOGIN_MAP.asMap().putIfAbsent(code, channel))); // code 不存在返回null
        return code;
    }
}
